 /* Copyright (C)
  * 2024 ZKLX Optoelectronic Technology Co.LTD
  * All rights reserved.
  */
 /**
  * @file lx_serial.cpp
  * @brief 串口通信接口实现
  * @author xuke
  * @version 1.0
  * @date 2024-04-19
  */

#include <iostream>
#include <cstring>
#include "lx_serial.h"

/*--------------------------------extern--------------------------------------------*/

/*--------------------------------globals-------------------------------------------*/

/*--------------------------------locals--------------------------------------------*/

/*--------------------------------funcs---------------------------------------------*/
/*
 * @brief 串口类构造函数(简版)
 * @param portadd 串口设备地址
 * @note 波特率 9600, 数据位 8, 停止位 1, 校验模式 NONE, 超时 100ms
 */
serial::serial(const std::string portadd)
	: portadd(portadd), speed(9600), databits(8), stopbits(1),
	parity(PARITY_NONE), readTimeout(1),
	isOpen(false)
{
}

/*
 * @brief 串口类构造函数
 * @param portadd 串口设备地址
 * @param baud 波特率
 * @param databits 数据位: 7, 8
 * @param stopbits 停止位: 1, 2
 * @param parity 校验模式: 见枚举值注释
 */
serial::serial(const std::string portadd, int baud, int databits, int stopbits, serial::parityEnum parity)
	: portadd(portadd), speed(baud), databits(databits), stopbits(stopbits),
	parity(parity), readTimeout(1),
	isOpen(false)
{
}

/*
 * @brief 串口类析构函数
 * @note 若析构前未关闭串口，会自动关闭
 */
serial::~serial()
{
	close();
}

/*
 * @brief 打开串口，并配置参数
 * @return 响应码: 0正常, -1打开串口失败, -2配置波特率失败, -3配置读写终端参数失败
 */
int serial::open()
{
	fd = ::open(portadd.c_str(), O_RDWR);
	if (fd < 0) {
		std::cerr << "serial.open: Open UART port " << portadd << " error: " << fd << std::endl;
		return -1;
	}
	if (setSpeed() == FALSE) {
		std::cerr << "serial.open: Set speed error." << std::endl;
		return -2;
	}
	if (setParity() == FALSE) {
		std::cerr << "serial.open: Set parity error." << std::endl;
		return -3;
	}
	tcflush(fd, TCIOFLUSH);
	isOpen = true;
	return 0;
}

/*
 * @brief 关闭串口
 */
void serial::close()
{
	if (isOpen) {
		isOpen = false;
		::close(fd);
	}
}

/*
 * @brief 设置每次调用read函数的超时时间
 * @param timeout 超时时间,百微妙(100us)
 * @return
 */
int serial::setReadTimeout(int timeout)
{
	struct termios options{};
	if (tcgetattr(fd, &options) != 0) {
		perror("SetupSerial 1");
		return (FALSE);
	}
	options.c_cc[VTIME] = timeout; // 设置超时
	options.c_cc[VMIN] = 0;
	tcflush(fd, TCIFLUSH);
	if (tcsetattr(fd, TCSANOW, &options) != 0) {
		perror("SetupSerial 3");
		return (FALSE);
	}
	tcflush(fd, TCIFLUSH);
	this->readTimeout = timeout;
	return TRUE;
}

int serial::send(const unsigned char *sendBuf, const int sendBytes)
{
	std::lock_guard<std::mutex> serialUseLock(useMutex);

	if (false == isOpen) {
		std::cout << "serial is not open!\n";
		return 0;
	}

	tcflush(fd, TCIOFLUSH);
	int sendRes = write(fd, sendBuf, sendBytes);
	if (sendRes != sendBytes) {
		std::cerr << "serial.duplexPairs: send error, res: " << sendRes << ", set sendBytes: " << sendBytes << std::endl;
		return -1;
	}
	return 0;
}

/*
 * @brief 发起一个传输交易(请求-响应模式)
 * @param sendBuf 发送数据的buffer
 * @param recvBuf 接收数据的buffer
 * @param sendBytes 发送的字节
 * @param recvBytes 预估接收的字节
 * @param timeout 设定整个传输的超时时间(ms),默认20ms
 * @return 响应码，0为正常, -1为发送错误, -2为接收错误
 *
 * @note 如果接收到的字节长度不满足recvByte规定的字节长度的话，函数会再次接收，直到满足长度
 * @note 但如果n次接收后都没有收到足够长的长度，函数会返回当前接收到的数据到recvBuf，并返回-2，n默认为5
 * @note 函数调用read的超时时间为100ms，即就算没有收到数据，100ms后也会结束read的调用
 *
 * @note duplexPairs内部有锁,使得该函数是线程安全的
 */
int serial::duplexPairs(const unsigned char *sendBuf, unsigned char *recvBuf, const int sendBytes, const int recvBytes, const int timeout)
{
	//std::cout << "sending...  ";
	std::lock_guard<std::mutex> serialUseLock(useMutex);
	tcflush(fd, TCIOFLUSH);
	int sendRes = write(fd, sendBuf, sendBytes);
	if (sendRes != sendBytes) {
		std::cerr << "serial.duplexPairs: send error, res: " << sendRes << ", set sendBytes: " << sendBytes
			<< std::endl;
		return -1;
	}
	unsigned char buf[256] = {0};
	auto recvTimeoutAt = std::chrono::steady_clock::now() + std::chrono::milliseconds(timeout);
	for (int recvLen=0, idx=0; recvLen < recvBytes; ) {
		int recvRes = read(fd, &buf, recvBytes);
		unsigned char *recvBufP = recvBuf + recvLen;

		int cpyLenMax = recvBytes - recvLen;
		int cpyLen = recvRes <= cpyLenMax ? recvRes : cpyLenMax;

		memcpy(recvBufP, buf, cpyLen);
		recvLen += cpyLen;
		//printf("%d %d %d %d \n", recvRes, cpyLenMax, cpyLen, recvLen);
		idx++;
		if (std::chrono::steady_clock::now() > recvTimeoutAt) {
			// 超时返回
			return -2;
		}
	}
	return 0;
}

/*
 * @brief 设置串口通信速率
 */
int serial::setSpeed()
{
	int baudSpeed = 0;
	if (baudMap.find(speed) == baudMap.end()) {
		std::cerr << "serial: No support baud: " << speed << " Use default baud: 9600" << std::endl;
		baudSpeed = baudMap.at(9600);
	} else {
		baudSpeed = baudMap.at(speed);
	}
	struct termios options{};

	if (tcgetattr(fd, &options) != 0) {
		perror("SetupSerial 1");
		return (FALSE);
	}
	tcflush(fd, TCIOFLUSH);
	cfsetispeed(&options, baudSpeed);
	cfsetospeed(&options, baudSpeed);
	int status = tcsetattr(fd, TCSANOW, &options);
	if (status != 0) {
		perror("tcsetattr fd1");
		return -1;
	}
	tcflush(fd, TCIOFLUSH);
	return 0;
}

/*
 * @brief 设置串口数据位，停止位和效验位
 */
int serial::setParity()
{
	struct termios options{};
	if (tcgetattr(fd, &options) != 0) {
		return (FALSE);
	}
	options.c_cflag &= ~CSIZE;
	switch (databits) /*设置数据位数*/
	{
		case 7:
			options.c_cflag |= CS7;
			break;
		case 8:
			options.c_cflag |= CS8;
			break;
		default:
			fprintf(stderr, "Unsupported data size\n");
			return (FALSE);
	}
	switch (parity) {
		case PARITY_NONE:
			options.c_cflag &= ~PARENB;     /* Clear parity enable */
			options.c_iflag &= ~INPCK;      /* Enable parity checking */
			break;
		case PARITY_ODD:
			options.c_cflag |= (PARODD | PARENB); /* 设置为奇效验*/
			options.c_iflag |= INPCK;             /* Disnable parity checking */
			break;
		case PARITY_EVEN:
			options.c_cflag |= PARENB;      /* Enable parity */
			options.c_cflag &= ~PARODD;     /* 转换为偶效验*/
			options.c_iflag |= INPCK;       /* Disnable parity checking */
			break;
		case PARITY_SPACE:
			options.c_cflag &= ~PARENB;
			options.c_cflag &= ~CSTOPB;
			break;
		default:
			fprintf(stderr, "Unsupported parity\n");
			return (FALSE);
	}
	/* 设置停止位*/
	switch (stopbits) {
		case 1:
			options.c_cflag &= ~CSTOPB;
			break;
		case 2:
			options.c_cflag |= CSTOPB;
			break;
		default:
			fprintf(stderr, "Unsupported stop bits\n");
			return (FALSE);
	}

	tcflush(fd, TCIFLUSH);

	options.c_cc[VTIME] = readTimeout; // 设置超时
	options.c_cc[VMIN] = 0; /* Update the options and do it NOW */
	options.c_lflag &= ~(ICANON | ECHO | ECHOE | ISIG);  /*Input*/
	options.c_oflag &= ~OPOST;   /*Output*/
	options.c_iflag &= ~(ICRNL | IXON);

	if (tcsetattr(fd, TCSANOW, &options) != 0) {
		perror("SetupSerial 3");
		return (FALSE);
	}
	return (TRUE);
}

